package com.beemans.common.ui.views.sticky

import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.min

/**
 * @author tiamosu
 * @date 2021/8/30.
 */
class HeaderScrollView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : NestedScrollView(context, attrs, defStyleAttr) {

    companion object {
        private val TAG = HeaderScrollView::class.java.simpleName
    }

    init {
        overScrollMode = OVER_SCROLL_NEVER
        isMotionEventSplittingEnabled = false
    }

    private var isNeedInvalidate = false
    private var isStickyLayout = false

    private lateinit var headView: ViewGroup
    private lateinit var contentView: ViewGroup

    private var contentRv: RecyclerView? = null
    private var unStickyRunnable: Runnable? = null

    /**
     * 固定吸顶监听
     */
    private var stickyFixedListener: ((isStickyFixed: Boolean) -> Unit)? = null

    /**
     * 设定 [NestedScrollView] 是否可滚动，默认为可滚动
     */
    private var isScrollable = true
        set(value) {
            field = value
            if (field) {
                stopScroll()
            }
            stickyFixedListener?.invoke(!field)
        }

    /**
     * 是否为吸顶模式
     */
    var isStickyMode = false
        set(value) {
            field = value
            requestLayout()
        }

    /**
     * 是否吸顶固定状态
     */
    fun isStickyFixed(): Boolean {
        return isStickyMode && !isScrollable
    }

    /**
     * 解除吸顶固定
     *
     * @param isScrollTop 是否滑动至顶部
     * @param scrollDurationMs [NestedScrollView] 滑动动画时间，默认为1000ms
     */
    fun unStickyFixed(isScrollTop: Boolean = false, scrollDurationMs: Int = 1000) {
        var delayMillis = 0L
        if (isScrollTop) {
            delayMillis = 200
            smoothScrollTo(0, 0, scrollDurationMs)
        }
        //执行smoothScrollTo过程中，可能触发吸顶固定回调，所以加了延迟
        removeCallbacks(unStickyRunnable)
        if (unStickyRunnable == null) {
            unStickyRunnable = Runnable { isScrollable = true }
        }
        postDelayed(unStickyRunnable, delayMillis)
    }

    /**
     * 设定视图固定吸顶监听
     */
    fun setStickyFixedListener(stickyFixedListener: (isStickyFixed: Boolean) -> Unit) {
        this.stickyFixedListener = stickyFixedListener
    }

    /**
     * 获取头部区域的高度
     */
    private val headViewHeight get() = if (headView.isVisible) headView.measuredHeight else 0

    private val headViewMinHeight get() = if (headView.isVisible) headView.minimumHeight else 0

    /**
     * 嵌套滚动布局的高度
     */
    private val contentViewHeight get() = if (contentView.isVisible) measuredHeight + headViewHeight - headViewMinHeight else measuredHeight

    override fun onFinishInflate() {
        super.onFinishInflate()
        val childView = getChildAt(0) as ViewGroup
        if (childView.childCount == 2) {
            headView = childView.getChildAt(0) as ViewGroup
            contentView = childView.getChildAt(1) as ViewGroup
            isStickyLayout = headView is StickyLinearLayout

            if (isStickyLayout) {
                headView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
                    (headView as? StickyLinearLayout)?.onScrollChange(
                        this, scrollX, scrollY, scrollX, scrollY
                    )
                }
            }
        } else {
            throw  IllegalStateException("$TAG is designed for nested scrolling and can only have two direct child")
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if (!isStickyMode) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
            return
        }
        val width = MeasureSpec.getSize(widthMeasureSpec)
        val height = MeasureSpec.getSize(heightMeasureSpec)
        setMeasuredDimension(width, height)
        headView.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
        measureChildren()
    }

    private fun measureChildren() {
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            child.measure(
                MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(contentViewHeight, MeasureSpec.EXACTLY)
            )
        }
    }

    override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) {
        if (!isStickyMode) {
            super.onOverScrolled(scrollX, scrollY, clampedX, clampedY)
            return
        }
        val scrollMax = headViewHeight - headView.minimumHeight
        super.onOverScrolled(scrollX, min(scrollMax, scrollY), clampedX, clampedY)
    }

    override fun scrollTo(x: Int, y: Int) {
        if (!isScrollable) {
            return
        }
        if (!isStickyMode) {
            super.scrollTo(x, y)
            return
        }
        val stickyFixedViewTop = (headView as? StickyLinearLayout)?.stickyFixedViewTop ?: 0
        if (stickyFixedViewTop != 0 && y >= stickyFixedViewTop) {
            isScrollable = false
            super.scrollTo(x, stickyFixedViewTop)
            return
        }
        val scrollMax = headViewHeight - headView.minimumHeight
        super.scrollTo(x, min(scrollMax, y))
    }

    override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
        if (!isStickyMode) {
            super.onNestedPreScroll(target, dx, dy, consumed, type)
            return
        }
        val isParentScroll = dispatchNestedPreScroll(dx, dy, consumed, null, type)
        if (!isParentScroll) {
            // 向上滑动且当前滑动距离小于顶部视图的高度时，需要此控件滑动响应的距离以保证滑动连贯性
            val scrollMax = headViewHeight - headView.minimumHeight
            val needKeepScroll = dy > 0 && scrollY < scrollMax
            if (needKeepScroll) {
                isNeedInvalidate = true
                scrollBy(0, dy)
                consumed[1] = dy
            }
        }
    }

    override fun onNestedScroll(
        target: View,
        dxConsumed: Int,
        dyConsumed: Int,
        dxUnconsumed: Int,
        dyUnconsumed: Int,
        type: Int,
        consumed: IntArray
    ) {
        isNeedInvalidate = true
        super.onNestedScroll(
            target,
            dxConsumed,
            dyConsumed,
            dxUnconsumed,
            dyUnconsumed,
            type,
            consumed
        )
    }

    override fun awakenScrollBars(): Boolean {
        return when {
            !isStickyMode -> super.awakenScrollBars()
            isNeedInvalidate -> {
                invalidate()
                true
            }
            else -> super.awakenScrollBars()
        }
    }

    override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
        super.onScrollChanged(l, t, oldl, oldt)
        if (isStickyMode && isStickyLayout) {
            (headView as? StickyLinearLayout)?.onScrollChange(this, l, t, oldl, oldt)
        }
    }

    override fun fling(velocityY: Int) {
        if (!isStickyMode) {
            super.fling(velocityY)
            return
        }
        // 不要用ScrollView的滚动，用RecyclerView可以保持连贯
        // super.fling(velocityY)
        findRecyclerView(contentView)?.let {
            if (it.canScrollVertically(1)) {
                it.fling(0, velocityY)
            } else {
                super.fling(velocityY)
            }
        } ?: super.fling(velocityY)
    }

    private fun findRecyclerView(contentView: ViewGroup): RecyclerView? {
        contentRv?.let { return it }
        if (contentView is RecyclerView && contentView.javaClass == RecyclerView::class.java) {
            contentRv = contentView
            return contentView
        }
        for (i in 0 until contentView.childCount) {
            val view = contentView.getChildAt(i)
            if (view is ViewGroup) {
                val target = findRecyclerView(view)
                if (target != null) {
                    contentRv = target
                    return target
                }
            }
        }
        return null
    }

    /**
     * 禁止继续滚动
     */
    fun stopScroll() {
        findRecyclerView(contentView)?.let {
            if (it.scrollState == RecyclerView.SCROLL_STATE_SETTLING) {
                it.stopScroll()
            }
            onStopNestedScroll(it, ViewCompat.TYPE_NON_TOUCH)
        }
    }

    override fun startNestedScroll(axes: Int, type: Int): Boolean {
        if (isStickyMode && isScrollable && type == ViewCompat.TYPE_TOUCH) {
            stopScroll()
        }
        return super.startNestedScroll(axes, type)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        if (!isScrollable) return false
        return super.onInterceptTouchEvent(ev)
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(ev: MotionEvent?): Boolean {
        if (!isScrollable) return false
        return super.onTouchEvent(ev)
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        removeCallbacks(unStickyRunnable)
        stickyFixedListener = null
    }
}